package com.guanyou.lock;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public abstract class IDLock {


    // java thread may be slow then using system thread-local
    public ThreadLocal<Long> threadId = new ThreadLocal<Long>() {
        @Override
        protected Long initialValue() {
            long _threadId = getThreadId();
            System.out.println(Thread.currentThread().getName() + ":" + _threadId + "@" + Thread.currentThread().getId());
            return _threadId;
        }
    };

//    public ThreadLocal<Long> threadId = ThreadLocal.withInitial(new Supplier<Long>() {
//        @Override
//        public Long get() {
//            long _threadId = (long) IDLock.this.getThreadId();
//            System.out.println(Thread.currentThread().getName() + ":" + _threadId + "@" + Thread.currentThread().getId());
//            return _threadId;
//        }
//    });


//    public ThreadLocal<Long> threadId = ThreadLocal.withInitial(() -> {
//                long _threadId = (long) IDLock.this.getThreadId();
//                System.out.println(Thread.currentThread().getName() + ":" + _threadId + "@" + Thread.currentThread().getId());
//                return _threadId;
//            }
//    );

    protected abstract long getThreadId();

    protected abstract void lock(Long res, Long threadId);

    protected abstract void unlock(Long res, Long threadId);

    public
    ThreadLocal<Map<Long, ILockable>> localLocks = new ThreadLocal<Map<Long, ILockable>>() {
        @Override
        protected Map<Long, ILockable> initialValue() {
            return new HashMap<Long, ILockable>();
        }
    };

    public void lock(Long... ids) {
        Arrays.sort(ids, 0, ids.length, new Comparator<Long>() {
            @Override
            public int compare(Long o1, Long o2) {
                long res = o2 - o1;
                return res > 0 ? 1 : (res == 0 ? 0 : -1);
            }
        });
        Map<Long, ILockable> localLocks = this.localLocks.get();
        for (Long id : ids) {
            if (!localLocks.containsKey(id)) {
                ILockable lock = new LockItem(this, id);
                lock.lock();
                localLocks.put(id, lock);
            }
        }
    }

    public void unlockAll() {
        Map<Long, ILockable> localLocks = this.localLocks.get();
        for (ILockable lock : localLocks.values()) {
            lock.unlock();
        }
        localLocks.clear();
    }


    static class LockItem implements ILockable {
        IDLock manager;
        Long res;
        AtomicInteger lockCount = new AtomicInteger(0);
        AtomicLong lockThreadId = new AtomicLong(0);

        LockItem(IDLock manager, long res) {
            this.manager = manager;
            this.res = res;
        }

        @Override
        public void lock() {
            long threadId = manager.threadId.get();
            if (lockCount.get() > 0 && lockThreadId.get() == threadId) {
                lockCount.incrementAndGet();
                return;
            }
            manager.lock(res, threadId);
            System.out.println(Thread.currentThread().getName() + ":" + threadId + " lock:" + res);
            lockThreadId.set(threadId);
            lockCount.set(1);
        }

        @Override
        public void unlock() {
            long threadId = manager.threadId.get();
            if (threadId == lockThreadId.get()) {
                if (lockCount.get() > 1) {
                    lockCount.decrementAndGet();
                    return;
                }
                assert lockCount.get() == 1;
                lockCount.set(0);
                lockThreadId.compareAndSet(threadId, 0);
                manager.unlock(res, threadId);
            }
        }

        @Override
        public long getPriority() {
            return res;
        }

        @Override
        public int getLockCount() {
            return lockCount.get();
        }
    }

    public static interface ILockable {
        void lock();

        void unlock();

        long getPriority();

        int getLockCount();
    }
}
